#include <stdio.h>
#include <stdlib.h>
#include <avr/io.h>
#include <util/delay.h>
#include <string.h>
#include "sio.h"

#define RS _BV(PB1)
#define RW _BV(PB2)
#define E _BV(PB3)
#define DB0 _BV(PC4)
#define DB1 _BV(PC3)
#define DB2 _BV(PC2)
#define DB3 _BV(PC1)
#define DB4 _BV(PC0)
#define DB5 _BV(PD7)
#define DB6 _BV(PD6)
#define DB7 _BV(PD5)

void all_Off(void)
{
	PORTC &= ~DB0 & ~DB1 & ~DB2 & ~DB3 & ~DB4;
	PORTD &= ~DB5 & ~DB6 & ~DB7;
}

void pulse_E(void)
{
	PORTB |= E;
	_delay_us(1);
	PORTB &= ~E;
}

void LCD_Write(uint8_t text)
{
	uint8_t mask = 0x80, i = 0;

	PORTB &= ~E & ~RW;
	PORTB |= RS;

	all_Off();

	while(mask > 0)
	{
		switch(i)
		{
			case(0):
				PORTD |= (text & mask) >> 2;
				break;
			case(1):
				PORTD |= (text & mask);
				break;
			case(2):
				PORTD |= (text & mask) << 2;
				break;
			case(3):
				PORTC |= (text & mask) >> 4;
				break;
			case(4):
				PORTC |= (text & mask) >> 2;
				break;
			case(5):
				PORTC |= (text & mask);
				break;
			case(6):
				PORTC |= (text & mask) << 2;
				break;
			case(7):
				PORTC |= (text & mask) << 4;
				break;
			default:
				break;
		}
		i++;
		mask >>= 1;
	}

	pulse_E();
	_delay_us(40);
}

// NOT WORKING PROPERLY!!!
// Does not allow writing text to the LCD while the display is turned off
void LCD_Display(uint8_t value)
{
	all_Off();
	PORTC |= DB3 | (value << 2) | DB1 | DB0;

	pulse_E();

	_delay_us(40);
}

void CLS_LCD(void)
{
	PORTB &= ~RS & ~RW & ~E; //Set RS, R/W, E to Low

	all_Off();

	PORTC |= DB0; //Clear screen

	pulse_E();

	_delay_ms(2); //Wait 2 ms
}

void set_Custom_Char(void)
{
	PORTB &= ~RS & ~RW & ~E; //Set RS, R/W, E to Low

	all_Off();

	PORTD |= DB6;

	pulse_E();
	_delay_us(40);

	LCD_Write(0x00);  // .....
	LCD_Write(0x10);  // *....
	LCD_Write(0x08);  // .*...
    LCD_Write(0x04);  // ..*..
    LCD_Write(0x02);  // ...*.
    LCD_Write(0x01);  // ....*
	LCD_Write(0x00);  // .....
	LCD_Write(0x00);  // .....

	LCD_Write(0x1F);  // *****
	LCD_Write(0x15);  // *.*.*
	LCD_Write(0x0A);  // .*.*.
    LCD_Write(0x00);  // .....
    LCD_Write(0x17);  // *.***
    LCD_Write(0x15);  // *.*.*
	LCD_Write(0x1D);  // ***.*
	LCD_Write(0x00);  // .....

	LCD_Write(0x00);  // .....
	LCD_Write(0x00);  // .....
	LCD_Write(0x00);  // .....
    LCD_Write(0x0D);  // .**.*
    LCD_Write(0x16);  // *.**.
    LCD_Write(0x00);  // .....
	LCD_Write(0x00);  // .....
	LCD_Write(0x00);  // .....

	LCD_Write(0x1F);  // *****
	LCD_Write(0x15);  // *.*.*
	LCD_Write(0x11);  // *...*
    LCD_Write(0x00);  // .....
    LCD_Write(0x1F);  // *****
    LCD_Write(0x04);  // ..*..
	LCD_Write(0x1F);  // *****
	LCD_Write(0x00);  // .....

	LCD_Write(0x0E);  // .***.
	LCD_Write(0x0A);  // .*.*.
	LCD_Write(0x0E);  // .***.
    LCD_Write(0x00);  // .....
    LCD_Write(0x00);  // .....
    LCD_Write(0x00);  // .....
	LCD_Write(0x00);  // .....
	LCD_Write(0x00);  // .....
}

void set_DDRAM(uint8_t hex)
{
	uint8_t mask = 0x40, i = 0;

	_delay_us(40);
	PORTB &= ~RS & ~RW & ~E; //Set RS, R/W, E to Low

	all_Off();

	PORTD |= DB7;

	while(mask > 0)
	{
		switch(i)
		{
			case(0):
				PORTD |= (hex & mask);
				break;
			case(1):
				PORTD |= (hex & mask) << 2;
				break;
			case(2):
				PORTC |= (hex & mask) >> 4;
				break;
			case(3):
				PORTC |= (hex & mask) >> 2;
				break;
			case(4):
				PORTC |= (hex & mask);
				break;
			case(5):
				PORTC |= (hex & mask) << 2;
				break;
			case(6):
				PORTC |= (hex & mask) << 4;
				break;
			default:
				break;
		}
		i++;
		mask >>= 1;
	}

	pulse_E();
	_delay_us(40);
}

void LCD_Init(void) //Initialize the LCD
{
	// Set Outputs
	DDRB |= RS | RW | E;
	DDRC |= DB0 | DB1 | DB2 | DB3 | DB4;
	DDRD |= DB5 | DB6 | DB7;

	_delay_ms(26); 
	_delay_ms(26);

	PORTB &= ~RS & ~RW & ~E;
	all_Off();

	_delay_ms(2); //Wait 2 ms

	// Set 8-bit Interface
	PORTD |= DB5;
	PORTC |= DB4 | DB3;

	pulse_E();

	// Wait 40 us
	_delay_us(40);

	// Set shift display or cursor
	all_Off();
	PORTC |= DB4 | DB3;

	pulse_E();

	_delay_us(40); //Wait 2 ms

	all_Off();
	PORTC |= DB0; //Clear screen

	pulse_E();

	_delay_ms(2); //Wait 2 ms

	all_Off();
	PORTC |= DB1; //Return Home

	pulse_E();

	_delay_ms(2); //Wait 2 ms

	all_Off();
	PORTC |= DB2 | DB1; //Entry Mode Set

	pulse_E();

	_delay_us(40);

	all_Off();
	PORTC |= DB3 | DB2 | DB1 | DB0; //Hide Cursor (Set PC1 = 1 to show cursor)

	pulse_E();

	_delay_us(40);

	set_Custom_Char();
	
	set_DDRAM(0);

	_delay_ms(1);
}

// NOT WORKING PROPERLY!!!
// This is returning 0b01111111 (127) (0x7F)
uint8_t get_Position(void)
{
	uint8_t position = 0;

	DDRC &= ~DB0 & ~DB1 & ~DB2 & ~DB3 & ~DB4;
	DDRD &= ~DB5 & ~DB6 & ~DB7;

	PORTB &= ~RS;
	PORTB |= RW;

	pulse_E();

	position = ((PINC & DB0) >> 4) |
			   ((PINC & DB1) >> 2) |
				(PINC & DB2) |
			   ((PINC & DB3) << 2) |
			   ((PINC & DB4) << 4) |
			   ((PIND & DB5) >> 2) |
				(PIND & DB6);

	DDRC |= DB0 | DB1 | DB2 | DB3 | DB4;
	DDRD |= DB5 | DB6 | DB7;

	return position;
}

void type_To_LCD(void)
{
	uint8_t i = 0, j = 0, c, qqq = 0;

	while(qqq < 3)
	{		
		if((i % 20) == 0)
		{
			j++;

			switch(j)
			{
				case(1):
					set_DDRAM(0x00);
					break;
				case(2):
					set_DDRAM(0x40);
					break;
				case(3):
					set_DDRAM(0x14);
					break;
				case(4):
					set_DDRAM(0x54);
					break;
				default:
					break;
			}
		
			i = 0;
		}

		if(j == 4)
		{
			j = 0;
		}

		i++;

		c = inchar();
		//printf("%c", c);

		if(c == 'q')
		{
			qqq++;
		}
		else
		{
			qqq = 0;
		}

		if(c == 0x5C)
			LCD_Write(0x08);
		else if(c == 0x08)
			LCD_Write(0x09);
		else if(c == 0x7E)
			LCD_Write(0x0A);
		else if(c == 0x0D)
			LCD_Write(0x0B);
		else
			LCD_Write(c);
	}
}

void print_LCD(char *text, uint16_t start, uint16_t end)
{
	uint8_t k, j = 0;

	for(k = start; k < end; k++)
	{
		if((k % 20) == 0)
		{
			j++;

			switch(j)
			{
				case(1):
					set_DDRAM(0x00);
					break;
				case(2):
					set_DDRAM(0x40);
					break;
				case(3):
					set_DDRAM(0x14);
					break;
				case(4):
					set_DDRAM(0x54);
					break;
				default:
					break;
			}
		}

		if(j == 4)
		{
			j = 0;
		}

		if(text[k] == 0x5C)
			LCD_Write(0x08);
		else if(text[k] == 0x08)
			LCD_Write(0x09);
		else if(text[k] == 0x7E)
			LCD_Write(0x0A);
		else if(text[k] == 0x0D)
			LCD_Write(0x0B);
		else
			LCD_Write(text[k]);
	}
}
